import scipy
import random
import copy
from helper_codes.general_stimulus import general_stimulus, GeneralStim

def sparse_noise_settings():
    '''
    This function tells you what settings are needed and in what order for this stimulus.
    Inputs:
        None
    Returns:
        settings        : A list of tupples.  In each tupple is
                            (setting_name, setting_description, default_value).
                          The list is of length num_settings + 1, so that the final
                             value in the list is True/False wether or not the stimulus
                             can be triggered via the parallel port or not.
    '''
    settings = []
    settings.append( ('grid_size',
                     'Number of checks.  Will create a nxn grid of checks. (best if power of 2)',
                     8 ))
    settings.append( ('update_period',
                     'How many frames between screen updates.',
                     3 ))
    settings.append( ('shades',
                     'Number of different shades of grey. (2 for black/white only)',
                     2 ))
    settings.append( ('background_shade',
                     'Background shade (-1,1.0) black to white.',
                    0.0 ))
    settings.append( ('num_checks',
                     'Number of checks drawn to the screen simultaneously.',
                    1))
    settings.append( ('duration',
                     'How long the stimulus will last. (seconds)',
                     20.0 ))
    settings.append( ('win_size',
                     'The dimensions of the stimulus delivery window. (pixels) [list]',
                     [1440,900] ))
    settings.append( ('refresh_rate',
                     'The number of screen refreshes per second. (Hz)',
                     60 ))
    settings.append( ('random_seed',
                     'The number to seed the random number generator.',
                     40 ))
    settings.append(True) # this stimulus can be triggered
    return settings


def sparse_noise_runtime(grid_size, update_period, shades, background_shade,
                         num_checks, duration, 
                         win_size, refresh_rate, random_seed): 
    """
    Calculate and return the number of seconds that this stimulus will need to run, after being triggered.
    """
    return duration + 0.99 # 0.99 added so rounding is always up.

class Stim(GeneralStim):
    """
    This is a basic stimulus object, it must have:
        .setup(window) method to set itself up to be drawn
        .draw() method to draw itself to the display
        .evolve() method to set itself up for the next draw
        .exhausted variable set to True after stim is done.
    """
    def __init__( self, grid_size, update_period, shades, background_shade,
                        num_checks, duration, refresh_rate, random_seed, 
                        preview, save_frames ):
        self.grid_size = grid_size
        self.update_period = update_period
        self.shades = shades
        self.background_shade = background_shade
        self.num_checks = num_checks
        self.duration = duration
        self.refresh_rate = refresh_rate

        random.seed(random_seed)
        self.preview = preview
        self.save_frames = save_frames

        self.exhausted = False
        self.times_evolved = 0
        self.times_since_update = 0
        self.update_index = 0

    def setup(self, window):
        from psychopy import visual

        # make a background PatchStim
        self.blank_tex = scipy.ones((2,2), dtype=scipy.float64)
        self.background_stim = visual.PatchStim( window, 
                                            tex=self.blank_tex * self.background_shade, 
                                            size=[1000,1000], units='norm' )
        
        # determine the possible shade values in a list
        shade_list = scipy.linspace(-1.0, 1.0, self.shades)

        # determine how many unique frames need to be drawn.
        num_frames = int(  self.duration * self.refresh_rate / 
                            (self.update_period * 1.0)  )
        self.total_frames = int( self.duration * self.refresh_rate )

        
        all_positions = []
        all_shades = []
        for i in xrange(num_frames):
            # generate unique positions for each check
            positions = set()
            positions.add( (random.randint(0, self.grid_size-1), 
                            random.randint(0, self.grid_size-1)) )
            while len(positions) < self.num_checks:
                positions.add( (random.randint(0, self.grid_size-1), 
                                random.randint(0, self.grid_size-1)) )
            positions = list(positions)
            # convert the integer into a 'norm' coordinate
            converted_positions = []
            for p in positions:
                converted_positions.append((-0.5 + (0.5+p[0])/(self.grid_size*1.0) ,
                     -0.5 + (0.5+p[1])/(self.grid_size*1.0) ))
            # get the shades of each check
            shades = [random.choice(shade_list) for i in xrange(self.num_checks)]
            all_positions.append(converted_positions)
            all_shades.append(shades)
        self.positions = all_positions
        self.shades = all_shades

        # make a list of checks to draw
        check_size = 1/(self.grid_size * 1.0)
        self.check_list = [ visual.PatchStim( window, 
                                        tex=self.blank_tex*self.shades[0][i], 
                                        pos=self.positions[0][i],
                                        size=[check_size,check_size], units='norm')  
                                    for i in xrange(self.num_checks)]
        self.textures = self.check_list


    def draw(self):
        self.background_stim.draw()
        for check in self.check_list:
            check.draw()

    def evolve(self):
        self.times_evolved += 1
        self.times_since_update += 1
        if self.times_since_update > self.update_period:
            self.update_index += 1
            self.times_since_update = 0
            for index, check in enumerate(self.check_list):
                check.setTex(self.blank_tex*self.shades[self.update_index][index])
                check.setPos(self.positions[self.update_index][index])

        if self.times_evolved >= self.total_frames-1:
            if (not self.preview) or self.save_frames:
                self.exhausted = True
            else:
                self.times_evolved = 0 # continue looping forever in preview mode.
                self.update_index = 0
                self.times_since_update = self.update_period

def sparse_noise(grid_size, update_period, shades, background_shade,
                 num_checks, duration, 
                 win_size, refresh_rate, random_seed,
                         window=None,
                         eye_cup=None, 
                         rf=None, 
                         save_frames=False,
                         save_frames_factor=1.0,
                         save_frames_size=100,
                         display_saved_frames=False,
                         preview=True):
    """
    This stimulus is a sparse noise pattern updating in a
        square the size of the receptive field.
    Inputs:
        grid_size       : number of checks.  Will create a nxn grid of checks. (best if power of 2)
        update_period   : how many frames between screen updates.
        shade           : number of different shades of grey. (2 for black/white only)
        background_shade: the shade of the background (-1,1.0) -1 black, 1 white
        num_checks      : number of checks drawn to the screen simultaneously.
        duration        : how long the stimulus will last. (seconds)
        win_size        : dimensions of the screen in pixels, (1440,900) is default
        refresh_rate    : refresh rate in Hz of the video, 60.0 is default.
        random_seed     : the seed for the random number generator.
        --- kwargs --
        window          : a psychopy.visual Window object, if not passed it will be constructed.
                        :    if passed it will not be closed (a blank screen will be drawn)
        eye_cup         : An Eye_cup object so we can draw the eye_cup on the screen and
                             set up the normalized coordinate system (size of eye_cup)
        rf              : an RF object so we can set up the normalized coordinate system
        frame_info      : if None, then no frames are saved, otherwise it is a 
                                dictionary with 
                                    'factor':float 
                                so we can scale down the frames and
                                output a frames.gif (an animated gif file)
                                and 
                                    'times':list
                                so we know how many frames to render.
        preview         : if True then the frames will display immediately,
                             otherwise we will wait for a pulse on the parallel port.
    Returns:
        times           : The times when frames were shown, with zero being the first frame time.
    """
    stim_list = [ Stim( grid_size, update_period, shades, background_shade,
                        num_checks, duration, refresh_rate, random_seed, 
                        preview, save_frames ) ]
    shade = background_shade
    return_values = general_stimulus( stim_list, shade, win_size,
                                         window=window,
                                         eye_cup=eye_cup, 
                                         rf=rf, 
                                         save_frames=save_frames,
                                         save_frames_factor=save_frames_factor,
                                         save_frames_size=save_frames_size,
                                         display_saved_frames=display_saved_frames,
                                         preview=preview)
    return return_values




if __name__ == '__main__':
    # ---------------------------------
    # -- Set the stimulus parameters --
    # ---------------------------------
    
    grid_size        = 10
    num_checks       = 2
    update_period    = 10
    shades           = 4 
    background_shade = 0.0
    duration         = 10.0

    # display parameters
    win_size = (1440,900)  # in pixels
    refresh_rate = 60.0    # in Hz
    random_seed = 40

    from helper_codes.RF import RF
    rf = RF.from_rf_file()

    sparse_noise(grid_size, update_period, shades, background_shade,
                 num_checks, duration, 
                 win_size, refresh_rate, random_seed,
                 preview=True) 
